﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using LynxWiki.Parser;

namespace LynxWiki.DomainModel
{
    public class TopicVersion
    {
        private string _body;
        private string _wikiText;
        private string _htmltext;

        public string TopicName { get; set; }
        public string WikiDir { get; set; }

        [DisplayFormat(DataFormatString="{0:yyyy-MM-dd HH:mm:ss}")]
        public DateTime CreatedOn { get; set; }
        public string Creator { get; set; }
        public int Version { get; set; }
        public Dictionary<string, string> Properties { get; set; }


        public string WikiText
        {
            get { return _wikiText; }
            set
            {
                _htmltext = string.Empty;
                _body = string.Empty;
                string temp = value.Replace("&nbsp;", "&#160;");
                _wikiText = temp;
                ExtractProperties();
            }
        }
        public string Body
        {
            get
            {
                if (string.IsNullOrEmpty(_body) && !string.IsNullOrEmpty(_wikiText)) {
                    _body = ParserEngine.FormattedTopic(_wikiText, WikiDir);
                    return _body;
                }
                else return _body;
            }
            set
            {
                _htmltext = String.Empty;
                _body = value;
            }
        }
        public string HtmlText
        {
            get { 
                if (string.IsNullOrEmpty(_htmltext)) {
                    _htmltext = ParserEngine.WikiToPresentation(this.Body, WikiDir, Properties);
                    return _htmltext;
                }
                else return _htmltext;
            }
            set
            {
                _htmltext = value;
            }
        }

        public List<string> Tags;
        public string EditReason { get; set; }
        public List<MinorUpdateInstance> MinorUpdates;
        public List<string> DenyReadPrivsUser; //if empty then no deny; if a user or a group a match may not read or update (even if update open to all)
        public List<string> DenyReadPrivsRole; //if empty then no deny; if a user or a group a match may not read or update (even if update open to all)
        public List<string> AllowUpdatePrivsUser; //if empty then all may update (except those denied read), or may be limited to a number of users and/or groups
        public List<string> AllowUpdatePrivsRole; //if empty then all may update (except those denied read), or may be limited to a number of users and/or groups


        public TopicVersion()
        {
            MinorUpdates = new List<MinorUpdateInstance>();
        }


        public TopicVersion(string name, string wikiDir, string wikitext, string creator, int version, string body = "", 
                string denyReadUser = "", string denyReadRole = "", string allowUpdateUser = "", string allowUpdateRole = "",
                string tags = "", string editRsn = "", List<MinorUpdateInstance> minUpdates = null, 
                string updater = "", string updateRsn = "")
        {
            this.TopicName = name;
            this.WikiDir = wikiDir;
            this.WikiText = wikitext;
            this.Body = body;
            this.Creator = creator;
            this.CreatedOn = DateTime.Now;
            this.Version = version;

            this.DenyReadPrivsUser = new List<string>();
            if (!string.IsNullOrEmpty(denyReadUser)) {
                string[] deny = denyReadUser.Split(',');
                if (deny.Count() > 0) {
                    foreach (string d in deny) {
                        DenyReadPrivsUser.Add(d.Trim());
                    }
                }
            }

            this.DenyReadPrivsRole = new List<string>();
            if (!string.IsNullOrEmpty(denyReadRole)) {
                string[] deny = denyReadRole.Split(',');
                if (deny.Count() > 0) {
                    foreach (string d in deny) {
                        DenyReadPrivsRole.Add(d.Trim());
                    }
                }
            }

            this.AllowUpdatePrivsUser = new List<string>();
            if (!string.IsNullOrEmpty(allowUpdateUser)) {
                string[] allow = allowUpdateUser.Split(',');
                if (allow.Count() > 0) {
                    foreach (string a in allow) {
                        AllowUpdatePrivsUser.Add(a.Trim());
                    }
                }
            }

            this.AllowUpdatePrivsRole = new List<string>();
            if (!string.IsNullOrEmpty(allowUpdateRole)) {
                string[] allow = allowUpdateRole.Split(',');
                if (allow.Count() > 0) {
                    foreach (string a in allow) {
                        AllowUpdatePrivsRole.Add(a.Trim());
                    }
                }
            }

            this.Tags = new List<string>();
            if (!string.IsNullOrEmpty(tags)) {
                string[] tag = tags.Split(',');
                if (tag.Count() > 0) {
                    foreach (string t in tag) {
                        Tags.Add(t.Trim());
                    }
                }
            }
            this.EditReason = editRsn;

            MinorUpdates = new List<MinorUpdateInstance>();
            if (minUpdates != null) {
                MinorUpdates = minUpdates;
            }
            if (string.IsNullOrEmpty(updater)) updater = "Not logged in";
            if (!string.IsNullOrEmpty(updateRsn)) {
                MinorUpdates.Add(new MinorUpdateInstance() { UpdatedOn = DateTime.Now, Updater = updater, UpdateReason = updateRsn });
            }
        }

        public TopicVersion(TopicVersion copy)
        {
            this.WikiText = copy.WikiText;
            this.Body = copy.Body;
            this.WikiDir = copy.WikiDir;
            this.Version = copy.Version;
            this.TopicName = copy.TopicName;
            this.EditReason = copy.EditReason;
            this.Creator = copy.Creator;
            this.CreatedOn = copy.CreatedOn;
            this.Tags = copy.Tags;
            this.MinorUpdates = copy.MinorUpdates;
            this.AllowUpdatePrivsRole = copy.AllowUpdatePrivsRole;
            this.AllowUpdatePrivsUser = copy.AllowUpdatePrivsUser;
            this.DenyReadPrivsRole = copy.DenyReadPrivsRole;
            this.DenyReadPrivsUser = copy.DenyReadPrivsUser;
        }

        public bool UserWriteAccess(IPrincipal user)
        {
            if (AllowUpdatePrivsUser == null && AllowUpdatePrivsRole == null) {
                return true;
            }
            if (AllowUpdatePrivsUser != null && AllowUpdatePrivsUser.Count == 0
                && AllowUpdatePrivsRole != null && AllowUpdatePrivsRole.Count == 0) {
                    return true;
            }
            if (AllowUpdatePrivsUser != null && AllowUpdatePrivsUser.Count > 0) {
                foreach (string aUser in AllowUpdatePrivsUser) {
                    if (aUser.ToLower() == user.Identity.Name.ToLower()) return true;
                }
            }
            if (AllowUpdatePrivsRole != null && AllowUpdatePrivsRole.Count > 0) {
                foreach (string aRole in AllowUpdatePrivsRole) {
                    if (user.IsInRole(aRole)) return true;
                }
            }
            return false;
        }

        public bool UserReadAccess(IPrincipal user)
        {
            if (user == null && (DenyReadPrivsUser != null || DenyReadPrivsRole != null)) return false;

            if (DenyReadPrivsUser == null && DenyReadPrivsRole == null) return true;

            if (user != null && user.Identity.GetType() == typeof(System.Security.Principal.GenericIdentity)) {
                if ((DenyReadPrivsUser != null && DenyReadPrivsUser.Count > 0) ||
                        (DenyReadPrivsRole != null && DenyReadPrivsRole.Count > 0)) {
                    return false;
                }
                else {
                    return true;
                }
            }

            if (DenyReadPrivsUser != null && DenyReadPrivsUser.Count == 0
                && DenyReadPrivsRole != null && DenyReadPrivsRole.Count == 0) {
                return true;
            }

            if (DenyReadPrivsUser != null && DenyReadPrivsUser.Count > 0) {
                foreach (string dUser in DenyReadPrivsUser) {
                    if (dUser.ToLower() == user.Identity.Name.ToLower()) return false;
                }
            }

            if (DenyReadPrivsRole != null && DenyReadPrivsRole.Count > 0) {
                foreach (string dRole in DenyReadPrivsRole) {
                    if (user.IsInRole(dRole)) return false;
                }
            }

            return true;
        }


        public TopicVersion(string xml)
        {
            XDocument doc = XDocument.Parse(xml);
            TopicVersion temp = (from item in doc.Descendants("topicVersion")
                                 select new TopicVersion {
                                     TopicName = (string)item.Element("topicName"),
                                     WikiDir = (string)item.Element("wikiDir"),
                                     Creator = (string)item.Element("creator"),
                                     CreatedOn = (DateTime)item.Element("createdOn"),
                                     Version = (int)item.Element("version"),
                                     EditReason = (string)item.Element("editReason"),
                                     WikiText = (string)item.Element("wikiText"),
                                     Body = (string)item.Element("body")
                                 }).First();
            List<string> tempTags = doc.Descendants("topicVersion").Elements("tags").Select(t => (string)t.Attribute("id")).ToList();
            List<MinorUpdateInstance> tempMinor = (from item in doc.Descendants("topicVersion").Elements("minorUpdate")
                                                   where item.HasAttributes
                                                   select new MinorUpdateInstance {
                                                       UpdateReason = (string)item.Attribute("updateReason"),
                                                       Updater = (string)item.Attribute("updater"),
                                                       UpdatedOn = (DateTime)item.Attribute("updatedOn")
                                                   }).ToList();
            List<string> tempDenyUser = doc.Descendants("topicVersion").Elements("denyUser").Select(t => (string)t.Attribute("id")).ToList();
            List<string> tempDenyRole = doc.Descendants("topicVersion").Elements("denyRole").Select(t => (string)t.Attribute("id")).ToList();
            List<string> tempAllowUser = doc.Descendants("topicVersion").Elements("allowUser").Select(t => (string)t.Attribute("id")).ToList();
            List<string> tempAllowRole = doc.Descendants("topicVersion").Elements("allowRole").Select(t => (string)t.Attribute("id")).ToList();
            this.TopicName = temp.TopicName;
            this.WikiDir = temp.WikiDir;
            this.Creator = temp.Creator;
            this.CreatedOn = temp.CreatedOn;
            this.Version = temp.Version;
            this.EditReason = temp.EditReason;
            this.WikiText = temp.WikiText;
            this.Body = temp.Body;
            this.Tags = tempTags;
            this.MinorUpdates = tempMinor;
            this.DenyReadPrivsUser = tempDenyUser;
            this.DenyReadPrivsRole = tempDenyRole;
            this.AllowUpdatePrivsUser = tempAllowUser;
            this.AllowUpdatePrivsRole = tempAllowRole;
        }

        public string BodyAsHtml()
        {
            throw new NotImplementedException();
        }

        public string ToXml()
        {
            XDocument doc = new XDocument(
                new XElement("topicVersion",
                    new XElement("topicName", TopicName),
                    new XElement("wikiDir", WikiDir),
                    new XElement("creator", Creator),
                    new XElement("createdOn", CreatedOn.ToString("yyyy-MM-ddTHH:mm:ss.fff")),
                    new XElement("version", Version.ToString()),
                    Tags.Select(tag => new XElement("tags", new XAttribute("id", tag))),
                    new XElement("editReason", EditReason),
                    MinorUpdates.Select(minorUpdates => new XElement("minorUpdate",
                            new XAttribute("updateReason", minorUpdates.UpdateReason),
                            new XAttribute("updater", minorUpdates.Updater),
                            new XAttribute("updatedOn", minorUpdates.UpdatedOn))),
                    new XElement("wikiText", WikiText),
                    new XElement("body", Body),
                    AllowUpdatePrivsUser.Select(allowUser => new XElement("allowUser", new XAttribute("id", allowUser))),
                    AllowUpdatePrivsRole.Select(allowRole => new XElement("allowRole", new XAttribute("id", allowRole))),
                    DenyReadPrivsUser.Select(denyUser => new XElement("denyUser", new XAttribute("id", denyUser))),
                    DenyReadPrivsRole.Select(denyRole => new XElement("denyRole", new XAttribute("id", denyRole)))
                    ));
            return doc.ToString(SaveOptions.None);
        }

        private static string regExSingleLineProperty = @"(^|\r\n|^:|\r\n:)(?<Name>[""]{0,2}[\p{Lu}][\p{L}\p{Nd}]*[""]{0,2}|[\p{Lu}][\p{L}\p{N}]+): (?<PropertyText>[^\r\n]*)\r\n";
        //private static string regExSingleLineProperty = @"(^|\r\n|^:|\r\n:)(?<Name>[\p{Lu}][\p{L}\p{N}]+): (?<PropertyText>[^\r\n]*)\r\n";
        private static string regExMultiLineProperty = @"(^|\r\n|^:|\r\n:)(?<Name>[""]{0,2}[\p{Lu}][\p{L}\p{Nd}]*[""]{0,2}|[\p{Lu}][\p{L}\p{N}]+):\[ (?<PropertyText>[^\[\]]*)\]\r\n";

        private Regex slpRegex = new Regex(regExSingleLineProperty, RegexOptions.Compiled | RegexOptions.Singleline);
        private Regex mlpRegex = new Regex(regExMultiLineProperty, RegexOptions.Compiled | RegexOptions.Multiline);

        public void ExtractProperties()
        {
            Dictionary<string, string> tempProps = new Dictionary<string, string>();
            string tempWiki = _wikiText;
            if (tempWiki.IndexOf("\r\n") < 0) tempWiki = tempWiki.Replace("\n", "\r\n");
            if (slpRegex.IsMatch(tempWiki)) {
                MatchCollection slpMatches = slpRegex.Matches(tempWiki);
                foreach (Match match in slpMatches) {
                    if (!tempProps.ContainsKey(match.Groups["Name"].Value)) {
                        tempProps.Add(match.Groups["Name"].Value, match.Groups["PropertyText"].Value);
                    }
                }
            }
            Properties = tempProps;
        }


    }

    public class MinorUpdateInstance
    {
        public string UpdateReason { get; set; }
        public string Updater { get; set; }
        public DateTime UpdatedOn { get; set; }

    }
}
